{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "OsP9ko_C8-3k"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "cellView": "form",
        "colab": {},
        "colab_type": "code",
        "id": "S7sxJvD18-3n"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "f6TuVE9J8-3s"
      },
      "source": [
        "# Using TensorFlow to run Monte Carlo Simulations on Black Scholes\n",
        "\n",
        "_Notebook originally contributed by: [Evan Hennis](https://github.com/ehennis)_\n",
        "\n",
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/examples/blob/master/community/en/MonteCarloBlackScholes.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/examples/tree/master/community/en/MonteCarloBlackScholes.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "sj46SIe88-3u"
      },
      "source": [
        "## Overview  \n",
        "\n",
        "This notebook is an update to my previous notebook about using verion 1 of TensorFlow to run Monte Carlo Simulations. You can find that notebook in the [original repo](https://github.com/ehennis/Blog/blob/master/BlackScholes/MonteCarloBlackScholes.ipynb) if you want the ins and outs. Be sure to read the heading where I reference Matthias Groncki who did the heavy lifting. In this notebook, I am going to convert the code to using TensorFlow v2."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "k_gghead8-3v"
      },
      "source": [
        "## TensorFlow 2.0  \n",
        "This is a \"port\" of TensorFlow 2.0. There is an [update utility](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/tools/compatibility) on the TensorFlow GitHub repository that will try and update your code. It isn't perfect but it does a good job of finding the libraries that have moved.  \n",
        "\n",
        "The ones it found in my code were 'tf.placeholder', 'tf.log', 'tf.session', and 'tf.cumprod'. The script just moved those to the 'tf.compat.v1' library. For me, I didn't want to use the compatible version if possible. "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "tqaqXI__8-3w"
      },
      "source": [
        "**Functions, not sessions**  \n",
        "The biggest change, by far, was not using *session.run* and converting all of the code to using functions. Along with this comes not using placeholders and using variables."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Fgri4YaH8-3w"
      },
      "source": [
        "**Variable, not placeholders**  \n",
        "With using variables and not the placeholders I had to think about the code as python versus the existing TensorFlow code. I need to create the variables OUTSIDE the method and the *assign* them within the method. If I didn't, the garbage collection would clear them out and cause issues."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "iV5tDogP8-3x"
      },
      "source": [
        "**TensorFlow Probability**  \n",
        "I was using *tf.contrib.distributions.Normal* and it was recommended that I start using the tensorflow_probability instead. The web site for more information is [https://www.tensorflow.org/probability/](https://www.tensorflow.org/probability/). My issue is that not everything has been ported over yet so I will have to wait until everything has been released."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "k7Z59fvX8-3y"
      },
      "source": [
        "**Stock Market/Black Scholes**  \n",
        "As a refresher, and to keep you from having to flip back and forth from this notebook to my previous notebook, here are some quick hits.  \n",
        "* Call Options: These are contracts that state that you will buy a stock at a certain price at a certain date and you will pay the option premium  \n",
        "* Black Scholes: A nobel prize winning financial formula that helps predict what the *correct* price of the option premium should be. The formula is: $$C(S,t) = S_tN(d_1) - Ke^{-r(T-t)}N(d_2)$$ This would then expand to:\n",
        "$$d_1 = \\frac{ln(\\frac{s}{K}) + (r + \\frac{\\sigma^2}{2}) (T-t)}{\\sigma \\sqrt{T-t}}$$  \n",
        "$$d_2 = d_1 - \\sigma \\sqrt{T-t} = \\frac{ln(\\frac{s}{K}) + (r - \\frac{\\sigma^2}{2}) (T-t)}{\\sigma \\sqrt{T-t}}$$  "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "AfqLdIxJ8-3z"
      },
      "source": [
        "## Setup"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "M0Mdtgj-8-34"
      },
      "outputs": [],
      "source": [
        "#Notebook Imports\n",
        "import tensorflow as tf\n",
        "assert tf.__version__.startswith('2')\n",
        "import tensorflow_probability as tfp \n",
        "import numpy as np\n",
        "import scipy.stats as stats\n",
        "import matplotlib.pyplot as plt"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "lLRQEgBR8-38"
      },
      "outputs": [],
      "source": [
        "print( tf.__version__)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "9_08Hrwf8-4B"
      },
      "source": [
        "**Python Black Scholes**  \n",
        "Here is how you would use python to calculate Black Scholes. With the change to v2 the TensorFlow version looks very similar."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "ezSQ_fUSMLOB"
      },
      "outputs": [],
      "source": [
        "#Formula Variables\n",
        "stock_price = 50.\n",
        "strike_price = 55.\n",
        "time_to_expire = 0.25\n",
        "implied_volitility = 0.2\n",
        "risk_free_rate_of_return = 0.02\n",
        "seed = 187"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "huChmOOM8-4C"
      },
      "outputs": [],
      "source": [
        "def blackscholes(stockPrice, strike, time_to_expr, imp_vol, risk_free):\n",
        "    #S: Stock Price\n",
        "    #K: Strike Price\n",
        "    #T: Time to Maturity\n",
        "    #Sigma: Volatility of underlying asset\n",
        "    #r: Interest Rate\n",
        "    \n",
        "    S = stockPrice\n",
        "    K = strike\n",
        "    T = time_to_expr\n",
        "    sigma = imp_vol\n",
        "    r = risk_free\n",
        "    \n",
        "    d_1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))\n",
        "    d_2 = (np.log(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))\n",
        "    return (S * stats.norm.cdf(d_1, 0.0, 1.0) - K * np.exp(-r * T) * stats.norm.cdf(d_2,0.0,1.0))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "dnRjeFZF8-4F"
      },
      "outputs": [],
      "source": [
        "blackscholes(stock_price,strike_price,time_to_expire,implied_volitility,risk_free_rate_of_return)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "5dR98jmO8-4I"
      },
      "source": [
        "**Results**  \n",
        "The today price that is returned is 0.52. That means that if the option is \"perfectly\" priced it would cost you \\$0.52."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "uIkAdJ308-4J"
      },
      "source": [
        "**TensorFlow Time!**  \n",
        "Now, we will do the same thing using TensorFlow. Note that we will return the price. I have removed the \"greek\" values from my old notebook because I didn't want to mess with them and I wanted to make this as straight forward as possible.  "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "1AT-NAmi8-4K"
      },
      "source": [
        "## [TFv2 Changes]\n",
        "`tf.placeholder` no longer exists so I need to create `tf.Variable`. I also need to create these OUTSIDE the method because python will garbage collect them. In my case though, I could remove the variable completely since you only need them for a mutable state.  \n",
        "\n",
        "`tf.log` -> `tf.math.log`.\n",
        "\n",
        "`tf.cumprod` -> `tf.math.cumprod`\n",
        "\n",
        "`tf.contrib.distributions.Normal` -> `tf.compat.v1.distributions.Normal`. This will eventually come from `TensorFlow Probability` but that library hasn't been built for v2 yet.  \n",
        "\n",
        "`Session` -> `Function`. This is obviously the largest change. With the addition of eager execution we no longer use the sessions.   \n",
        "\n",
        "`bs_tf = blackscholes` -> `tf.function(blackscholes_tf2)`. As stated above, I need to add tf.function to my method call.  \n",
        "\n",
        "Here is a good link from the TF GitHub page: [https://github.com/tensorflow/docs/blob/7c9d49ee188c67a315deaf92ebd41fd0f3b15c4a/site/en/r2/guide/effective_tf2.md](https://github.com/tensorflow/docs/blob/7c9d49ee188c67a315deaf92ebd41fd0f3b15c4a/site/en/r2/guide/effective_tf2.md)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "zI4n83378-4L"
      },
      "outputs": [],
      "source": [
        "def blackscholes_tf2(stockPrice, strike, time_to_expr, imp_vol, risk_free, show_greeks = True):\n",
        "    #Formulas using TensorFlow libraries instead of numpy\n",
        "    d_1 = (tf.math.log(stockPrice / strike) + (risk_free + 0.5 * imp_vol ** 2) * time_to_expr) / (imp_vol * tf.sqrt(time_to_expr))\n",
        "    d_2 = (tf.math.log(stockPrice / strike) + (risk_free - 0.5 * imp_vol ** 2) * time_to_expr) / (imp_vol * tf.sqrt(time_to_expr))\n",
        "    target_calc = stockPrice * tfp.distributions.Normal(0.,1.).cdf(d_1) - strike * tf.exp(-risk_free * time_to_expr) * tfp.distributions.Normal(0.,1.).cdf(d_2)\n",
        "    return target_calc"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "Ef4JZ9hU8-4O",
        "scrolled": false
      },
      "outputs": [],
      "source": [
        "bs_tf = tf.function(blackscholes_tf2)\n",
        "bs_tf(stock_price,strike_price,time_to_expire,implied_volitility,risk_free_rate_of_return).numpy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "2z1QEvjCMLOV"
      },
      "source": [
        "**Note**  \n",
        "You will get a warning about `tensorflow.python.ops.distributions.normal` being deprecated. As stated above the probability package wasn't available when this was created so I needed to keep the `tf.compat.v1.distributions` class. Once that is available we can update this method."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hmkIg9L48-4R"
      },
      "source": [
        "**Results**  \n",
        "As you can see we again get \\$0.52. This matches what the python function returned. You will also see that there were some warnings. These will be fixed when I can use the TensorFlow Probability library."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "HMOAAtGi8-4S"
      },
      "source": [
        "**Monte Carlo Time!!**  "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "ecjxl1uYMLOa"
      },
      "outputs": [],
      "source": [
        "def black_scholes_formula(stockPrice, strike, time_to_expr, imp_vol, risk_free, timedelta, stdnorm_random_variates):\n",
        "    return stockPrice * tf.math.cumprod(tf.exp((risk_free - 0.5 * imp_vol ** 2) * timedelta + imp_vol * tf.sqrt(timedelta) * stdnorm_random_variates), axis = 1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "8IdT7QBu8-4V"
      },
      "outputs": [],
      "source": [
        "def make_path_simulator(stockPrice, strike, time_to_expr, imp_vol, risk_free, seed, n_sims, obs):\n",
        "    #Create the time variables\n",
        "    if seed != 0:\n",
        "        np.random.seed(seed)\n",
        "    stdnorm_random_variates = np.random.randn(n_sims, obs)\n",
        "    timedelta = time_to_expr / stdnorm_random_variates.shape[1]\n",
        "    return black_scholes_formula(stockPrice, strike, \n",
        "                                 time_to_expr, imp_vol, risk_free, timedelta, stdnorm_random_variates)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "JP15kPKI8-4Y"
      },
      "outputs": [],
      "source": [
        "path_simulator = tf.function(make_path_simulator)\n",
        "paths = path_simulator(stock_price,strike_price,\n",
        "                       time_to_expire,implied_volitility,risk_free_rate_of_return, seed, 10, 400)\n",
        "plt.figure(figsize=(8,5))\n",
        "_ = plt.plot(np.transpose(paths))\n",
        "_ = plt.title('Simulated Path')\n",
        "_ = plt.ylabel('Price')\n",
        "_ = plt.xlabel('TimeStep')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "zgknXnC78-4b"
      },
      "outputs": [],
      "source": [
        "#Check if the expected value of the simulation is close to the expected mean\n",
        "path_simulator = tf.function(make_path_simulator)\n",
        "paths = path_simulator(stock_price,strike_price,\n",
        "                       time_to_expire,implied_volitility,risk_free_rate_of_return, seed, 10000000, 2)\n",
        "assert np.mean(paths[:, 1])-np.exp(0.03*2)*100<0.01,'Value is too large'"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "collapsed": true,
        "id": "sHTy41ai8-4g"
      },
      "source": [
        "**Pricing the option**  \n",
        "Now, we are going to price the option by extending the previous function"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "UVogW1jU8-4h"
      },
      "outputs": [],
      "source": [
        "def create_plain_vanilla_mc_tf_pricer(stockPrice, strike, time_to_expr, imp_vol, risk_free, seed, n_sims, obs):\n",
        "    if seed != 0:\n",
        "        np.random.seed(seed)\n",
        "    stdnorm_random_variates = np.random.randn(n_sims, 1)\n",
        "    timedelta = time_to_expr / stdnorm_random_variates.shape[1]\n",
        "    S_T = black_scholes_formula(stockPrice, strike, time_to_expr, imp_vol, risk_free, timedelta, stdnorm_random_variates)\n",
        "    payout = tf.maximum(S_T[:,-1] - strike, 0)\n",
        "    npv = tf.exp(-risk_free*time_to_expr) * tf.reduce_mean(payout)\n",
        "    target_calc = npv\n",
        "    return target_calc"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "j-J8l0fr8-4m"
      },
      "outputs": [],
      "source": [
        "sims = 1000000\n",
        "observations = 1\n",
        "\n",
        "plain_vanilla_mc_tf_pricer = tf.function(create_plain_vanilla_mc_tf_pricer)\n",
        "plain_vanilla_mc_tf_pricer(stock_price,strike_price,time_to_expire,implied_volitility,risk_free_rate_of_return, seed, sims,observations).numpy()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qZ48-jzM8-4p"
      },
      "source": [
        "**Conclusion**  \n",
        "We have verified that our TensorFlow implementation gets the same solution as the python implementation. I have also shown how to convert the *session.run* to using *functions*. Many thanks to Matthias for inspiring me to dig into TensorFlow using Monte Carlo Simulations. Thanks to Paige Bailey [@DynamicWebPaige](https://twitter.com/DynamicWebPaige) and Martin Wicke [@martin_wicke](https://twitter.com/martin_wicke)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "MonteCarloBlackScholes.ipynb",
      "private_outputs": true,
      "provenance": [],
      "toc_visible": true,
      "version": "0.3.2"
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
